# -*- coding: utf-8 -*-
"""
文件名称: data_filter_from_log.py

功能概述:
    在已有“疾病过滤日志”的基础上, 对新的 Alpaca 数据集进行再次过滤。

    已有日志格式示例 (来自上一次自适应过滤的输出日志):
        {
          "等效删除比": 0.3,
          "疾病情况": [
            ["病理性近视", 2434, 0.95],
            ["年龄相关性黄斑变性", 2134, 0.95],
            ["黄斑前膜", 1500, 0.8],
            ...
          ]
        }

    其中:
        - "疾病情况" 为一个列表, 每个元素为 [疾病名称, 该疾病在旧数据中的频次, 删除比例]
        - 删除比例 delete_ratio ∈ [0,1], 保留比例 keep_ratio = 1 - delete_ratio

    本脚本的过滤规则:
        1. 依然依赖支持文件 support_cleaned.json 提供的图像 -> 疾病标签信息:
            - support_cleaned 示例:
                {
                  "aptos_001639a390f0.png": {
                    "image_path": ".../aptos_001639a390f0.png",
                    "label": ["增殖型糖尿病视网膜病变"],
                    "quality": null
                  },
                  "messidor_20060411_58550_0200_PP.png": {
                    "image_path": ".../messidor_20060411_58550_0200_PP.png",
                    "label": null,
                    "quality": "图像质量不可评估..."
                  },
                  ...
                }
            - 对于 label 为非空 list 的样本:
                * 将元素转为 str, 排序后用 "_" join 得到 disease_key (疾病名称字符串)
            - 对于 label 为 None 或空列表 [] 的样本:
                * 视为“无有效疾病标签”, 不生成 disease_key, 也不参与疾病统计.

        2. 根据已有“疾病过滤日志”中的疾病情况, 构造疾病级别的保留比例:
            - 从日志中读出:
                disease_delete_ratio_log[disease_key] = delete_ratio
            - 对每个疾病 key 计算:
                keep_ratio_log[disease_key] = 1.0 - delete_ratio
            - 在新数据中:
                * 若某张图像对应的疾病 key 在 keep_ratio_log 中能找到:
                      -> 使用日志中该疾病的 keep_ratio 进行过滤
                * 若找不到:
                      -> 使用统一的 not_found_keep_ratio (默认 1.0, 即不过滤)

        3. 对 Alpaca 格式 JSON 数据 (顶层 list[dict]) 进行过滤:
            - 输入 JSON 示例:
                [
                  {
                    "question_type": "compare",
                    "instruction": "...",
                    "input": "",
                    "output": "...",
                    "images": ["/path/to/1979327_l_eye.jpg"]
                  },
                  ...
                ]
            - 对于每条样本:
                * 从 images[0] 取得图像路径, 用 os.path.basename 提取文件名 img_name
                * 根据支持文件, 尝试得到其疾病 key:
                      - 若 label 非空: 可得到 disease_key
                      - 若 label 为 None/[] 或支持文件中不存在该 img_name: disease_key = None
                * 再根据疾病 key 与日志/默认值决定 keep_ratio:
                      - 若 disease_key 在 keep_ratio_log 中:
                            keep_ratio = keep_ratio_log[disease_key]
                      - 若 disease_key 为空 / 不在日志中:
                            keep_ratio = not_found_keep_ratio  (默认 1.0)
                * 对于特定题型 FILTER_TYPES (默认 ["judge", "compare", "choice"]):
                      - 生成 r ~ U[0,1)
                      - 若 r <= keep_ratio -> 保留该样本
                      - 否则丢弃
                * 对于不在 FILTER_TYPES 内的题型 (如 diagnosis / quality+diagnosis / quality):
                      - 一律保留, 不做随机裁剪

        4. 统计与日志输出:
            - 在过滤结束后, 计算等效删除比例:
                * 全局等效删除比例:
                    global_delete_ratio = filtered / total
                * 仅 FILTER_TYPES 上的等效删除比例:
                    filtered_types_delete_ratio = sum(filtered_qtype) / sum(total_qtype)
            - 同时统计“新数据集中的疾病频次” (按图像级别, 以图像为计数单位):
                * 对每条样本, 若能在支持文件中找到 disease_key:
                    - 将其图像文件名加入该 disease_key 对应的集合 (用于去重)
                * 最终频次 = 每个 disease_key 对应集合的大小 (即“有多少张图像出现了这种疾病”)
            - 根据是否在日志中出现, 拆分为两类:
                ① 已在日志中匹配的疾病: found_diseases
                    [疾病名称, 频次(新数据集中图像数), 使用的保留比例 keep_ratio]
                ② 未在日志中出现过的疾病: not_found_diseases
                    [疾病名称, 频次(新数据集中图像数), 使用的保留比例 keep_ratio]
                  (默认情况下, keep_ratio = 1.0, 即不做删除)
            - 写出新的统计日志 JSON, 文件名为:
                  <输出文件名(stem)>_refilter_log.json
              结构示例:
                {
                  "全局等效删除比": 0.25,
                  "仅过滤题型等效删除比": 0.35,
                  "已匹配疾病情况": [
                    ["病理性近视", 1200, 0.05],
                    ["年龄相关性黄斑变性", 800, 0.05],
                    ...
                  ],
                  "未匹配疾病情况": [
                    ["黄斑前膜", 30, 1.0],
                    ...
                  ]
                }

使用示例:
    python data_filter_from_log.py \
        -i /path/to/new_alpaca.json \
        -o /path/to/new_alpaca_filtered.json \
        -s /path/to/cls_annotations_merged.json \
        -l /path/to/prev_filter_log.json \
        --not-found-keep-ratio 1.0 \
        --seed 42
"""

import argparse
import json
import logging
import os
import random
from collections import defaultdict
from pathlib import Path
from typing import Any, Dict, List, Optional, Set, Tuple


# -----------------------------
# 全局配置
# -----------------------------

# 需要按疾病进行过滤的题目类型 (与之前脚本保持一致)
FILTER_TYPES = ["judge", "compare", "choice"]

logger = logging.getLogger(__name__)
if not logger.handlers:
    _handler = logging.StreamHandler()
    _formatter = logging.Formatter(
        fmt="[%(asctime)s] [%(levelname)s] [%(name)s] %(message)s",
        datefmt="%Y-%m-%d %H:%M:%S",
    )
    _handler.setFormatter(_formatter)
    logger.addHandler(_handler)
    logger.setLevel(logging.INFO)


# -----------------------------
# 支持文件读取与疾病 key 构造
# -----------------------------

def load_support_cleaned(support_path: Path) -> Dict[str, Any]:
    """
    读取支持 JSON 文件, 返回原始 dict。

    参数:
        support_path: 支持 JSON 文件路径

    返回:
        support_dict: 原始 JSON 解析后的 dict

    异常:
        - 文件不存在或无法读取时抛出异常
        - JSON 解析失败时抛出异常
        - 顶层结构不是 dict 时抛出异常
    """
    if not support_path.is_file():
        raise FileNotFoundError(f"support_cleaned_path 文件不存在: {support_path}")

    try:
        text = support_path.read_text(encoding="utf-8")
        data = json.loads(text)
    except Exception as e:
        raise RuntimeError(f"读取或解析支持 JSON 失败: path={support_path}, error={e}") from e

    if not isinstance(data, dict):
        raise RuntimeError(
            f"支持 JSON 顶层结构必须为 dict, 实际为: {type(data)} (path={support_path})"
        )

    return data


def build_image_to_disease_key(
    support_dict: Dict[str, Any]
) -> Dict[str, Optional[str]]:
    """
    基于支持 JSON, 构造每个图像文件名 -> 疾病 key 的映射。

    疾病 key 的构造方式:
        - 对于 support_dict 中的每个条目:
            * key: 图像文件名, 如 "aptos_001639a390f0.png"
            * value: 记录 dict, 其中 "label" 字段为疾病标签列表
        - 对 label 字段:
            * 若 label 为 None 或空列表 []:
                  视为“无有效疾病标签”, 映射为 None
            * 若 label 为非空 list:
                  将其中元素转为 str, 排序后用 "_" join, 得到 disease_key

    返回:
        image_to_disease_key: {image_filename: disease_key 或 None}

    异常:
        - 若 label 既不是 None 也不是 list 时抛出异常
    """
    image_to_disease_key: Dict[str, Optional[str]] = {}

    for img_name, info in support_dict.items():
        if not isinstance(info, dict):
            raise RuntimeError(
                f"support_cleaned 中 key={img_name!r} 对应的 value 非 dict 类型: {type(info)}"
            )

        labels = info.get("label", None)

        if labels is None:
            # 无标签, 映射为 None
            image_to_disease_key[img_name] = None
            continue

        if isinstance(labels, list):
            if len(labels) == 0:
                # 空列表, 也视为“无有效疾病标签”
                image_to_disease_key[img_name] = None
                continue

            labels_sorted = sorted(str(x) for x in labels)
            disease_key = "_".join(labels_sorted)
            image_to_disease_key[img_name] = disease_key
            continue

        # 其它类型 -> 直接报错
        raise RuntimeError(
            f"support_cleaned 中 key={img_name!r} 的 label 类型异常, "
            f"期望为 list / None, 实际为: {type(labels)}, value={labels!r}"
        )

    logger.info(
        "支持文件解析完成: 共 %d 个图像条目 (包含有/无疾病标签的情况)",
        len(image_to_disease_key),
    )
    return image_to_disease_key


# -----------------------------
# 从旧日志中读取疾病级别的保留比例
# -----------------------------

def load_disease_keep_ratio_from_log(
    log_path: Path,
) -> Dict[str, float]:
    """
    从上一次过滤产生的日志 JSON 中读取 “疾病情况” 列表, 构造疾病级别的保留比例字典。

    日志格式示例:
        {
          "等效删除比": 0.3,
          "疾病情况": [
            ["病理性近视", 2434, 0.95],
            ["年龄相关性黄斑变性", 2134, 0.95],
            ["黄斑前膜", 1500, 0.8],
            ...
          ]
        }

    其中第三个数视为“删除比例 delete_ratio”, 保留比例 keep_ratio = 1 - delete_ratio。

    返回:
        disease_keep_ratio: {disease_key: keep_ratio ∈ [0,1]}

    异常:
        - 找不到 "疾病情况" 字段时抛出异常
        - 格式不合法时抛出异常
    """
    if not log_path.is_file():
        raise FileNotFoundError(f"日志文件不存在: {log_path}")

    try:
        text = log_path.read_text(encoding="utf-8")
        data = json.loads(text)
    except Exception as e:
        raise RuntimeError(f"读取或解析日志 JSON 失败: path={log_path}, error={e}") from e

    if not isinstance(data, dict):
        raise RuntimeError(
            f"日志 JSON 顶层结构必须为 dict, 实际为: {type(data)} (path={log_path})"
        )

    disease_items = data.get("疾病情况", None)
    if not isinstance(disease_items, list):
        raise RuntimeError(
            f"日志 JSON 中缺少 '疾病情况' 字段或其类型不是 list: {type(disease_items)}"
        )

    disease_keep_ratio: Dict[str, float] = {}
    for idx, item in enumerate(disease_items, 1):
        if (
            not isinstance(item, list)
            or len(item) < 3
        ):
            raise RuntimeError(
                f"日志 JSON 中 '疾病情况' 第 {idx} 项格式非法, 期望为 [name, freq, delete_ratio], 实际为: {item!r}"
            )
        name = item[0]
        delete_ratio = item[2]
        if not isinstance(name, str):
            raise RuntimeError(
                f"日志 JSON 中 '疾病情况' 第 {idx} 项的疾病名称不是字符串: {name!r}"
            )
        try:
            dr = float(delete_ratio)
        except Exception as e:
            raise RuntimeError(
                f"日志 JSON 中 '疾病情况' 第 {idx} 项的删除比例无法转为 float: {delete_ratio!r}, error={e}"
            ) from e

        # 保留比例 = 1 - 删除比例, 并裁剪到 [0,1]
        keep_ratio = 1.0 - dr
        if keep_ratio < 0.0:
            keep_ratio = 0.0
        if keep_ratio > 1.0:
            keep_ratio = 1.0

        disease_keep_ratio[name] = keep_ratio

    logger.info(
        "从日志中读取到 %d 个疾病的保留比例信息",
        len(disease_keep_ratio),
    )
    return disease_keep_ratio


def build_image_keep_ratio_from_log(
    image_to_disease_key: Dict[str, Optional[str]],
    disease_keep_ratio_log: Dict[str, float],
    not_found_keep_ratio: float,
) -> Dict[str, float]:
    """
    基于: (1) 支持文件中的图像 -> 疾病 key 映射,
         (2) 日志中的疾病 keep_ratio 信息,
         (3) 对未出现在日志中的疾病使用统一 not_found_keep_ratio,
    为每张图像构造最终的保留比例 image_keep_ratio。

    参数:
        image_to_disease_key: {image_filename: disease_key 或 None}
        disease_keep_ratio_log: {disease_key: keep_ratio} (来自旧日志)
        not_found_keep_ratio: 未在日志中出现的疾病所采用的统一 keep_ratio, 默认 1.0

    返回:
        image_keep_ratio: {image_filename: keep_ratio}
    """
    if not (0.0 < not_found_keep_ratio <= 1.0):
        raise ValueError(
            f"not_found_keep_ratio 必须在 (0,1] 区间内, 当前值为: {not_found_keep_ratio!r}"
        )

    image_keep_ratio: Dict[str, float] = {}
    for img_name, d_key in image_to_disease_key.items():
        if d_key is None:
            # 无疾病标签的图像: 视为“未匹配疾病”, 使用 not_found_keep_ratio
            image_keep_ratio[img_name] = float(not_found_keep_ratio)
        else:
            kr = disease_keep_ratio_log.get(d_key, not_found_keep_ratio)
            image_keep_ratio[img_name] = float(kr)

    logger.info(
        "已为 %d 张图像分配 keep_ratio (来自日志疾病信息 + 未匹配疾病统一比例)",
        len(image_keep_ratio),
    )
    return image_keep_ratio


# -----------------------------
# 过滤主逻辑 (基于日志的 image_keep_ratio)
# -----------------------------

def filter_alpaca_with_log(
    records: List[Dict[str, Any]],
    image_keep_ratio: Dict[str, float],
    image_to_disease_key: Dict[str, Optional[str]],
    disease_keep_ratio_log: Dict[str, float],
    not_found_keep_ratio: float,
    seed: int = 42,
) -> Tuple[List[Dict[str, Any]], Dict[str, Any]]:
    """
    使用来自日志的 image_keep_ratio 对 Alpaca 记录进行过滤, 并返回过滤后的记录及统计信息。

    参数:
        records:              输入的数据列表 (顶层 list[dict])
        image_keep_ratio:     图像级别的保留比例 {image_filename: keep_ratio}
        image_to_disease_key: 支持文件中的图像 -> 疾病 key 映射 {image_filename: disease_key 或 None}
        disease_keep_ratio_log: 日志中的疾病 keep_ratio 信息 {disease_key: keep_ratio}
        not_found_keep_ratio: 未出现在日志中的疾病统一使用的 keep_ratio
        seed:                 随机种子

    返回:
        filtered_records: 过滤后的记录列表
        stats:             统计信息, 结构示例:
            {
              "total": int,
              "kept": int,
              "filtered": int,
              "global_delete_ratio": float,
              "filtered_types_delete_ratio": float,
              "type_stats": {...},
              "disease_image_freq_in_input": {disease_key: 图像数},
            }
    """
    random.seed(seed)

    total = 0
    kept = 0
    filtered = 0
    type_stats: Dict[str, Dict[str, int]] = {}
    filtered_records: List[Dict[str, Any]] = []

    # 按疾病统计“在当前输入数据集中出现过的图像数量”(去重, 以图像为单位)
    disease_to_image_set: Dict[str, Set[str]] = defaultdict(set)

    for idx, record in enumerate(records, 1):
        if not isinstance(record, dict):
            raise RuntimeError(
                f"索引 {idx} 的元素不是 dict 类型, 实际为: {type(record)}"
            )
        total += 1

        q_type = record.get("question_type", None)
        if not isinstance(q_type, str):
            raise RuntimeError(
                f"索引 {idx} 缺少 question_type 字段或类型非 str: {q_type!r}"
            )

        if q_type not in type_stats:
            type_stats[q_type] = {"total": 0, "kept": 0, "filtered": 0}
        type_stats[q_type]["total"] += 1

        images = record.get("images", None)
        if not isinstance(images, list) or len(images) == 0:
            raise RuntimeError(
                f"索引 {idx} 的 images 字段缺失或为空, 无法获取图像路径: {images!r}"
            )
        img_path = images[0]
        if not isinstance(img_path, str):
            raise RuntimeError(
                f"索引 {idx} 的 images[0] 不是字符串, 实际为: {img_path!r}"
            )
        img_name = os.path.basename(img_path)

        # 统计疾病在本数据集中的图像频次 (疾病 key 可能为 None; None 则不计入疾病统计)
        d_key = image_to_disease_key.get(img_name, None)
        if d_key is not None:
            disease_to_image_set[d_key].add(img_name)

        # 非过滤类型 => 一律保留
        if q_type not in FILTER_TYPES:
            filtered_records.append(record)
            kept += 1
            type_stats[q_type]["kept"] += 1
            continue

        # 过滤类型 => 依据 image_keep_ratio 做随机采样
        if img_name not in image_keep_ratio:
            raise RuntimeError(
                f"索引 {idx} 的图像文件名在 image_keep_ratio 中未找到: img_name={img_name!r}。"
                f"请检查支持文件是否完整, 以及构建 image_keep_ratio 的逻辑。"
            )
        keep_ratio = image_keep_ratio[img_name]

        r = random.random()
        if r <= keep_ratio:
            filtered_records.append(record)
            kept += 1
            type_stats[q_type]["kept"] += 1
        else:
            filtered += 1
            type_stats[q_type]["filtered"] += 1

    # 计算等效删除比例
    if total > 0:
        global_delete_ratio = filtered / float(total)
    else:
        global_delete_ratio = 0.0

    sub_total = 0
    sub_filtered = 0
    for q_type, st in type_stats.items():
        if q_type in FILTER_TYPES:
            sub_total += st["total"]
            sub_filtered += st["filtered"]
    if sub_total > 0:
        sub_delete_ratio = sub_filtered / float(sub_total)
    else:
        sub_delete_ratio = 0.0

    logger.info(
        "过滤完成: 总样本数=%d, 保留=%d, 丢弃=%d",
        total,
        kept,
        filtered,
    )
    for q_type, st in sorted(type_stats.items(), key=lambda x: x[0]):
        logger.info(
            "  question_type=%s: total=%d, kept=%d, filtered=%d",
            q_type,
            st["total"],
            st["kept"],
            st["filtered"],
        )

    logger.info(
        "等效删除比例(全局): delete_ratio=%.4f (filtered=%d / total=%d)",
        global_delete_ratio,
        filtered,
        total,
    )
    logger.info(
        "等效删除比例(仅 FILTER_TYPES=%s): delete_ratio=%.4f (filtered=%d / total=%d)",
        ",".join(FILTER_TYPES),
        sub_delete_ratio,
        sub_filtered,
        sub_total,
    )

    # 组装疾病在当前输入数据集中的图像频次 (以图像为单位计数)
    disease_image_freq_in_input: Dict[str, int] = {
        d_key: len(img_set) for d_key, img_set in disease_to_image_set.items()
    }

    stats: Dict[str, Any] = {
        "total": total,
        "kept": kept,
        "filtered": filtered,
        "global_delete_ratio": global_delete_ratio,
        "filtered_types_delete_ratio": sub_delete_ratio,
        "type_stats": type_stats,
        "disease_image_freq_in_input": disease_image_freq_in_input,
        "not_found_keep_ratio": float(not_found_keep_ratio),
    }
    return filtered_records, stats


# -----------------------------
# CLI 入口与主流程
# -----------------------------

def parse_args() -> argparse.Namespace:
    """
    命令行参数解析。

    参数:
        --input-json, -i            输入 Alpaca JSON 文件路径 (顶层 list[dict])
        --output-json, -o           输出过滤后的 JSON 文件路径 (顶层 list[dict])
        --support-cleaned-path, -s  支持 JSON 文件路径 (提供图像 -> 疾病标签映射)
        --log-path, -l              上一次过滤产生的疾病日志 JSON 路径 (包含“疾病情况”字段)
        --not-found-keep-ratio      对于未在日志中出现的疾病使用的统一保留比例 (默认 1.0)
        --seed                      随机种子 (默认 42)
    """
    parser = argparse.ArgumentParser(
        description="基于已有疾病过滤日志(data_filter_log)的 Alpaca 数据再次过滤工具",
    )
    parser.add_argument(
        "--input-json",
        "-i",
        type=str,
        required=True,
        help="输入 Alpaca JSON 文件路径 (顶层 list[dict])",
    )
    parser.add_argument(
        "--output-json",
        "-o",
        type=str,
        required=True,
        help="输出过滤后的 JSON 文件路径 (顶层 list[dict])",
    )
    parser.add_argument(
        "--support-cleaned-path",
        "-s",
        type=str,
        required=True,
        help="支持 JSON 文件路径, 提供图像 -> 疾病标签信息 (key 为图像文件名, 含 label 字段)",
    )
    parser.add_argument(
        "--log-path",
        "-l",
        type=str,
        required=True,
        help="上一次过滤产生的日志 JSON 路径 (包含 '疾病情况' 字段)",
    )
    parser.add_argument(
        "--not-found-keep-ratio",
        type=float,
        default=1.0,
        help="对于在日志中未找到的疾病, 使用的统一保留比例 (默认 1.0, 即不过滤)",
    )
    parser.add_argument(
        "--seed",
        type=int,
        default=42,
        help="随机种子 (默认 42)",
    )
    return parser.parse_args()


def main() -> None:
    """主函数: 串联支持信息读取、日志解析与 Alpaca 数据过滤, 并输出新的统计日志。"""
    args = parse_args()

    input_path = Path(args.input_json)
    output_path = Path(args.output_json)
    support_path = Path(args.support_cleaned_path)
    log_path = Path(args.log_path)
    not_found_keep_ratio = float(args.not_found_keep_ratio)
    seed = int(args.seed)

    # 1. 读取支持 JSON 与旧日志
    support_dict = load_support_cleaned(support_path)
    image_to_disease_key = build_image_to_disease_key(support_dict)
    disease_keep_ratio_log = load_disease_keep_ratio_from_log(log_path)
    image_keep_ratio = build_image_keep_ratio_from_log(
        image_to_disease_key=image_to_disease_key,
        disease_keep_ratio_log=disease_keep_ratio_log,
        not_found_keep_ratio=not_found_keep_ratio,
    )

    # 2. 读取输入 Alpaca JSON (顶层 list[dict])
    if not input_path.is_file():
        raise FileNotFoundError(f"输入 JSON 文件不存在: {input_path}")
    try:
        raw_records = json.loads(input_path.read_text(encoding="utf-8"))
    except Exception as e:
        raise RuntimeError(
            f"读取或解析输入 JSON 失败: path={input_path}, error={e}"
        ) from e

    if not isinstance(raw_records, list):
        raise RuntimeError(
            f"输入 JSON 顶层结构必须为 list, 实际为: {type(raw_records)} (path={input_path})"
        )

    # 3. 基于日志的过滤
    filtered_records, stats = filter_alpaca_with_log(
        records=raw_records,
        image_keep_ratio=image_keep_ratio,
        image_to_disease_key=image_to_disease_key,
        disease_keep_ratio_log=disease_keep_ratio_log,
        not_found_keep_ratio=not_found_keep_ratio,
        seed=seed,
    )

    # 4. 写出过滤后的 JSON
    try:
        output_path.parent.mkdir(parents=True, exist_ok=True)
        output_path.write_text(
            json.dumps(filtered_records, ensure_ascii=False, indent=2),
            encoding="utf-8",
        )
        logger.info("过滤后的 Alpaca JSON 已写出: %s", output_path)
    except Exception as e:
        raise RuntimeError(
            f"写出过滤结果 JSON 失败: path={output_path}, error={e}"
        ) from e

    # 5. 构造并写出新的统计日志:
    #    - 全局等效删除比
    #    - 仅 FILTER_TYPES 等效删除比
    #    - 已匹配疾病情况: [疾病名称, 频次(新数据集中图像数), 使用的保留比例 keep_ratio]
    #    - 未匹配疾病情况: 同上 (保留比例通常为 not_found_keep_ratio, 默认 1.0)
    disease_image_freq_in_input: Dict[str, int] = stats.get("disease_image_freq_in_input", {})

    found_diseases: List[List[Any]] = []
    not_found_diseases: List[List[Any]] = []

    for d_key, freq in disease_image_freq_in_input.items():
        if d_key in disease_keep_ratio_log:
            keep_ratio = disease_keep_ratio_log[d_key]
            found_diseases.append([d_key, int(freq), float(keep_ratio)])
        else:
            keep_ratio = not_found_keep_ratio
            not_found_diseases.append([d_key, int(freq), float(keep_ratio)])

    # 按频次从高到低排序
    found_diseases_sorted = sorted(found_diseases, key=lambda x: x[1], reverse=True)
    not_found_diseases_sorted = sorted(not_found_diseases, key=lambda x: x[1], reverse=True)

    log_obj: Dict[str, Any] = {
        "全局等效删除比": float(stats.get("global_delete_ratio", 0.0)),
        "仅过滤题型等效删除比": float(stats.get("filtered_types_delete_ratio", 0.0)),
        "已匹配疾病情况": found_diseases_sorted,
        "未匹配疾病情况": not_found_diseases_sorted,
        "过滤题型": FILTER_TYPES,
        "未匹配疾病保留比例默认值": float(not_found_keep_ratio),
    }

    log_output_path = output_path.parent / f"{output_path.stem}_refilter_log.json"
    try:
        log_output_path.write_text(
            json.dumps(log_obj, ensure_ascii=False, indent=2),
            encoding="utf-8",
        )
        logger.info("基于旧日志的重新过滤统计已写出: %s", log_output_path)
    except Exception as e:
        raise RuntimeError(
            f"写出重新过滤日志 JSON 失败: path={log_output_path}, error={e}"
        ) from e


if __name__ == "__main__":
    main()
